Мета: Засвоєння принципів, знайомвство з
інструментами та набуття навичок манпулювання даними
(wrangle) засобами мови програмування R та
колекції пакетів tidyvers.
Імпорт даних
# install_formats() #інсталяція додаткових компонентів пакету rio
library(rio)
df <- data.frame(x = 1:5, y = rnorm(5))
export(df, "data/df_data_frame.txt")
dfImp <- import("data/df_data_frame.txt")
dfImp
data("mtcars") #підключення стандартного набору даних mtcars
# head(mtcars)
export(head(mtcars), "data/mtcars.dta")
convert('data/mtcars.dta', 'data/mtcars.csv')
import("data/mtcars.csv")
Імпорт з реляційних баз даних
#install.packages("dbplyr")
#install.packages("RSQLite")
library(dbplyr)
library(dplyr)
Attaching package: 'dplyr'
The following objects are masked from 'package:dbplyr':
ident, sql
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
library(RSQLite)
# my_db <- src_sqlite("data/my_db.sqlite3", create = T)
#head(my_db)
#install.packages("nycflights13")
library(nycflights13)
# flights_sqlite <- copy_to(my_db, flights, temporary = FALSE,
# indexes = list(c("year", "month", "day"), "carrier", "tailnum"))
# head(flights_sqlite)
con <- DBI::dbConnect(RSQLite::SQLite(), path = "data/my_db.sqlite3")
flights_sqlite <- copy_to(con, nycflights13::flights, "flights",
temporary = FALSE,
indexes = list(
c("year", "month", "day"),
"carrier",
"tailnum",
"dest"
)
)
head(flights_sqlite)
Приведення даних до охайного вигляду
#install.packages("tidyverse")
library(tidyverse)
Warning: package 'ggplot2' was built under R version 4.4.2
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ forcats 1.0.0 ✔ readr 2.1.5
✔ ggplot2 3.5.1 ✔ stringr 1.5.1
✔ lubridate 1.9.3 ✔ tibble 3.2.1
✔ purrr 1.0.2 ✔ tidyr 1.3.1
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::ident() masks dbplyr::ident()
✖ dplyr::lag() masks stats::lag()
✖ dplyr::sql() masks dbplyr::sql()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# відносний критерій на 10000
table1 %>% # стандартний набір даних
mutate(rate = cases / population * 10000) # обчислення нового поля
# кількість випадків на рік
table1 %>%
count(year, wt = cases)
# Візуалізація динаміки зміни кількості випадків з часом
library(ggplot2)
ggplot(table1, aes(year, cases)) +
geom_line(aes(group = country), colour = "white") +
geom_point(aes(colour = country))+
theme_dark()

Завдання на самостійну роботу.
Побудувати динаміку відносного критерію rate кількості
захворювань по роках для кожної держави.
table1 %>% # стандартний набір даних
mutate(rate = cases / population * 10000) |>
ggplot(aes(year, rate)) +
geom_line(aes(group = country), colour = "white") +
geom_point(aes(colour = country))+
theme_dark()

Gathering
table4a %>%
gather(`1999`, `2000`, key = "year", value = "cases")
tidy4a <- table4a %>%
gather(`1999`, `2000`, key = "year", value = "cases")
tidy4b <- table4b %>%
gather(`1999`, `2000`, key = "year", value = "population")
dplyr::left_join(tidy4a, tidy4b)
Joining with `by = join_by(country, year)`
Завдання на самостійну роботу
Виконати попереднє завдання, базуючись на таблицях
tidy4a і tidy4b з використанням потокового
оператора.
dplyr::left_join(table4a |>
gather(`1999`, `2000`, key = "year", value = "cases")
,table4b |>
gather(`1999`, `2000`, key = "year", value = "population")) |>
mutate(rate = cases / population * 10000) |>
ggplot(aes(year, rate)) +
geom_line(aes(group = country), colour = "white") +
geom_point(aes(colour = country))+
theme_dark()
Joining with `by = join_by(country, year)`

Spreading
table2 %>%
spread(key = type, value = count)
Separating
table3 %>%
separate(rate, into = c("cases", "population"))
Uniting
table5 %>%
unite(year, century, year, sep = "")
Пропущені значення
Пропущені значення (missing value) у наборах даних
можуть бути двох видів: явні (позначені як NA,
Not Available) і неявні (просто не представлені у
даних). Такі дані називаються некомплектні.
Нижче наведено приклад, який це ілюструє.
stocks <- tibble(
year = c(2015, 2015, 2015, 2015, 2016, 2016, 2016),
qtr = c( 1, 2, 3, 4, 2, 3, 4),
return = c(1.88, 0.59, 0.35, NA, 0.92, 0.17, 2.66)
)
stocks
Дані за четвертий квартал 2015 явно відсутні про що свідчить
відповідне значення. Дані за перший квартал не внесені у таблицю, тобто
відсутні неявно, але відсутність можна помітити після відповідної
траснформації.
stocks %>%
spread(year, return)
Виявити множину некомплектних даних можна також з використанням
функції complete().
stocks %>%
complete(year, qtr)
Проблема некомплектних даних вирішується двома шляхами: виключенням
некомплектних спостережень, або імпутацією пропущених значень іншими
значеннями, виходячи з певної моделі.
stocks %>%
spread(year, return) %>%
gather(year, return, `2015`:`2016`, na.rm = TRUE)
У випадках, коли це доцільно, можна використовувати функцію
fill(), яка заповнює пропущенні значення, взявши значення з
останньої заповненої клітинки:
df <- data.frame(Month = 1:12, Year = c(2000, rep(NA, 11)))
df
Трансформація
# Вибірка рядків таблиці
library(dplyr)
starwars %>%
filter(species == "Droid")
# Вибірка полів таблиці
starwars %>%
select(name, ends_with("color"))
# Створення нового поля у таблиці з послідуючою вибіркою
starwars %>%
mutate(name, bmi = mass / ((height / 100) ^ 2)) %>%
select(name:mass, bmi)
# Сортування даних
starwars %>%
arrange(desc(mass))
# Обчислення агрегатів з попереднім групуванням по полю species
starwars %>%
group_by(species) %>%
summarise(
n = n(),
mass = mean(mass, na.rm = TRUE)
) %>%
filter(n > 1)
Індивідуальне завдання
library(readxl)
library(dplyr)
library(plotly)
Attaching package: 'plotly'
The following object is masked from 'package:ggplot2':
last_plot
The following object is masked from 'package:rio':
export
The following object is masked from 'package:stats':
filter
The following object is masked from 'package:graphics':
layout
library(DT)
library(lubridate)
# Завантаження даних із очищеного Excel файлу
data <- read_excel("cleaned_currency_data.xlsx")
# Додаємо колонку з місяцем для середньомісячних значень
data <- data %>% mutate(місяць = floor_date(Дата, "month"))
# Розрахунок середньомісячних курсів
monthly_avg <- data %>%
group_by(місяць) %>%
summarise(
USD_UAH_avg = mean(USD_UAH, na.rm = TRUE),
EUR_UAH_avg = mean(EUR_UAH, na.rm = TRUE),
RUB_UAH_avg = mean(RUB_UAH, na.rm = TRUE)
)
# Інтерактивна таблиця щоденних курсів
daily_table <- datatable(data, options = list(pageLength = 10, autoWidth = TRUE))
# Інтерактивна таблиця середньомісячних курсів
monthly_table <- datatable(monthly_avg, options = list(pageLength = 10, autoWidth = TRUE))
# Інтерактивний графік щоденних курсів
daily_plot <- plot_ly(data, x = ~Дата) %>%
add_lines(y = ~USD_UAH, name = "USD/UAH") %>%
add_lines(y = ~EUR_UAH, name = "EUR/UAH") %>%
add_lines(y = ~RUB_UAH, name = "RUB/UAH") %>%
layout(title = "Щоденна динаміка курсів валют", xaxis = list(title = "Дата"), yaxis = list(title = "Курс"))
# Інтерактивний графік середньомісячних курсів
monthly_plot <- plot_ly(monthly_avg, x = ~місяць) %>%
add_lines(y = ~USD_UAH_avg, name = "USD/UAH") %>%
add_lines(y = ~EUR_UAH_avg, name = "EUR/UAH") %>%
add_lines(y = ~RUB_UAH_avg, name = "RUB/UAH") %>%
layout(title = "Середньомісячна динаміка курсів валют", xaxis = list(title = "Місяць"), yaxis = list(title = "Середній курс"))
# Виведення таблиць і графіків
print(daily_table)
print(monthly_table)
daily_plot
LS0tDQp0aXRsZTogItCb0LDQsdC+0YDQsNGC0L7RgNC90LAg0YDQvtCx0L7RgtCwIOKEljIuINCc0LDQvdGW0L/Rg9C70Y7QstCw0L3QvdGPINC00LDQvdC40LzQuCINCmF1dGhvcjogIlvQmtC+0YHRgtGW0L0g0IQu0JIuXWh0dHBzOi8vZ2l0aHViLmNvbS9OaWNrV2F5bmUwMiksIGByIGZvcm1hdChTeXMudGltZSgpLCAnJVknKWAiDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6DQojICAgcGRmX2RvY3VtZW50Og0KIyAgICAgaGlnaGxpZ2h0OiB0YW5nbw0KIyAgICAgdG9jOiB5ZXMNCiMgICB3b3JkX2RvY3VtZW50Og0KIyAgICAgaGlnaGxpZ2h0OiB0YW5nbw0KIyAgICAgdG9jOiB5ZXMNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHllcyAjINCz0LXQvdC10YDQsNGG0ZbRjyDQt9C80ZbRgdGC0YMg0LTQvtC60YPQvNC10L3RgtGDDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgaGlnaGxpZ2h0OiB0YW5nbyAjINCa0L7Qu9GW0YAg0L/RltC00YHQstGW0YfRg9Cy0LDQvdC90Y8g0LrQvtC00YMNCmZvbnRzaXplOiAxMnB0ICMg0YDQvtC30LzRltGAINGI0YDQuNGE0YLRgw0KaGVhZGVyLWluY2x1ZGVzOg0KIFx1c2VwYWNrYWdlW1QyQV17Zm9udGVuY30NCiBcdXNlcGFja2FnZVt1dGY4XXtpbnB1dGVuY30NCmVkaXRvcl9vcHRpb25zOiANCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGNvbnNvbGUgIyDQstC40LLRltC0INGA0LXQt9GD0LvRjNGC0LDRgtGW0LIg0L7QsdGH0LjRgdC70LXQvdGMINC90LAg0LrQvtC90YHQvtC70YwNCi0tLQ0KDQpfX9Cc0LXRgtCwOl9fIF/Ql9Cw0YHQstC+0ZTQvdC90Y8g0L/RgNC40L3RhtC40L/RltCyLCDQt9C90LDQudC+0LzQstGB0YLQstC+INC3INGW0L3RgdGC0YDRg9C80LXQvdGC0LDQvNC4INGC0LAg0L3QsNCx0YPRgtGC0Y8g0L3QsNCy0LjRh9C+0Log0LzQsNC90L/Rg9C70Y7QstCw0L3QvdGPINC00LDQvdC40LzQuCAoX193cmFuZ2xlX18pINC30LDRgdC+0LHQsNC80Lgg0LzQvtCy0Lgg0L/RgNC+0LPRgNCw0LzRg9Cy0LDQvdC90Y8gYFJgINGC0LAg0LrQvtC70LXQutGG0ZbRlyDQv9Cw0LrQtdGC0ZbQsiBgdGlkeXZlcnNgLl8gDQoNCiMjIyDQhtC80L/QvtGA0YIg0LTQsNC90LjRhQ0KDQpgYGB7cn0NCiMgaW5zdGFsbF9mb3JtYXRzKCkgI9GW0L3RgdGC0LDQu9GP0YbRltGPINC00L7QtNCw0YLQutC+0LLQuNGFINC60L7QvNC/0L7QvdC10L3RgtGW0LIg0L/QsNC60LXRgtGDIHJpbw0KDQpsaWJyYXJ5KHJpbykNCmRmIDwtIGRhdGEuZnJhbWUoeCA9IDE6NSwgeSA9IHJub3JtKDUpKQ0KDQpleHBvcnQoZGYsICJkYXRhL2RmX2RhdGFfZnJhbWUudHh0IikNCmRmSW1wIDwtIGltcG9ydCgiZGF0YS9kZl9kYXRhX2ZyYW1lLnR4dCIpDQpkZkltcA0KYGBgDQoNCg0KYGBge3J9DQoNCmRhdGEoIm10Y2FycyIpICPQv9GW0LTQutC70Y7Rh9C10L3QvdGPINGB0YLQsNC90LTQsNGA0YLQvdC+0LPQviDQvdCw0LHQvtGA0YMg0LTQsNC90LjRhSBtdGNhcnMNCiMgaGVhZChtdGNhcnMpDQpleHBvcnQoaGVhZChtdGNhcnMpLCAiZGF0YS9tdGNhcnMuZHRhIikNCmNvbnZlcnQoJ2RhdGEvbXRjYXJzLmR0YScsICdkYXRhL210Y2Fycy5jc3YnKQ0KaW1wb3J0KCJkYXRhL210Y2Fycy5jc3YiKQ0KYGBgDQoNCiMjIyMg0IbQvNC/0L7RgNGCINC3INGA0LXQu9GP0YbRltC50L3QuNGFINCx0LDQtyDQtNCw0L3QuNGFDQoNCmBgYHtyfQ0KI2luc3RhbGwucGFja2FnZXMoImRicGx5ciIpDQojaW5zdGFsbC5wYWNrYWdlcygiUlNRTGl0ZSIpDQoNCmxpYnJhcnkoZGJwbHlyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoUlNRTGl0ZSkNCg0KIyBteV9kYiA8LSBzcmNfc3FsaXRlKCJkYXRhL215X2RiLnNxbGl0ZTMiLCBjcmVhdGUgPSBUKQ0KI2hlYWQobXlfZGIpDQpgYGANCg0KYGBge3J9DQojaW5zdGFsbC5wYWNrYWdlcygibnljZmxpZ2h0czEzIikNCmxpYnJhcnkobnljZmxpZ2h0czEzKQ0KIyBmbGlnaHRzX3NxbGl0ZSA8LSBjb3B5X3RvKG15X2RiLCBmbGlnaHRzLCB0ZW1wb3JhcnkgPSBGQUxTRSwgDQojICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5kZXhlcyA9IGxpc3QoYygieWVhciIsICJtb250aCIsICJkYXkiKSwgImNhcnJpZXIiLCAidGFpbG51bSIpKQ0KIyBoZWFkKGZsaWdodHNfc3FsaXRlKQ0KYGBgDQoNCmBgYHtyfQ0KY29uIDwtIERCSTo6ZGJDb25uZWN0KFJTUUxpdGU6OlNRTGl0ZSgpLCBwYXRoID0gImRhdGEvbXlfZGIuc3FsaXRlMyIpDQpmbGlnaHRzX3NxbGl0ZSA8LSBjb3B5X3RvKGNvbiwgbnljZmxpZ2h0czEzOjpmbGlnaHRzLCAiZmxpZ2h0cyIsDQogICAgICAgIHRlbXBvcmFyeSA9IEZBTFNFLCANCiAgICAgICAgaW5kZXhlcyA9IGxpc3QoDQogICAgICAgICAgYygieWVhciIsICJtb250aCIsICJkYXkiKSwgDQogICAgICAgICAgImNhcnJpZXIiLCANCiAgICAgICAgICAidGFpbG51bSIsDQogICAgICAgICAgImRlc3QiDQogICAgICAgICkNCikNCg0KaGVhZChmbGlnaHRzX3NxbGl0ZSkNCmBgYA0KDQpgYGB7cn0NCkRCSTo6ZGJEaXNjb25uZWN0KGNvbikNCmBgYA0KIA0KIyMjINCf0YDQuNCy0LXQtNC10L3QvdGPINC00LDQvdC40YUg0LTQviDQvtGF0LDQudC90L7Qs9C+INCy0LjQs9C70Y/QtNGDDQoNCg0KYGBge3J9DQojaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNCg0KYGBge3J9DQojINCy0ZbQtNC90L7RgdC90LjQuSDQutGA0LjRgtC10YDRltC5INC90LAgMTAwMDANCnRhYmxlMSAlPiUgICMg0YHRgtCw0L3QtNCw0YDRgtC90LjQuSDQvdCw0LHRltGAINC00LDQvdC40YUNCiAgbXV0YXRlKHJhdGUgPSBjYXNlcyAvIHBvcHVsYXRpb24gKiAxMDAwMCkgIyDQvtCx0YfQuNGB0LvQtdC90L3RjyDQvdC+0LLQvtCz0L4g0L/QvtC70Y8NCg0KDQoNCiMg0LrRltC70YzQutGW0YHRgtGMINCy0LjQv9Cw0LTQutGW0LIg0L3QsCDRgNGW0LoNCnRhYmxlMSAlPiUgDQogIGNvdW50KHllYXIsIHd0ID0gY2FzZXMpDQpgYGANCg0KYGBge3J9DQojINCS0ZbQt9GD0LDQu9GW0LfQsNGG0ZbRjyDQtNC40L3QsNC80ZbQutC4INC30LzRltC90Lgg0LrRltC70YzQutC+0YHRgtGWINCy0LjQv9Cw0LTQutGW0LIg0Lcg0YfQsNGB0L7QvA0KbGlicmFyeShnZ3Bsb3QyKQ0KDQpnZ3Bsb3QodGFibGUxLCBhZXMoeWVhciwgY2FzZXMpKSArIA0KICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gY291bnRyeSksIGNvbG91ciA9ICJ3aGl0ZSIpICsgDQogIGdlb21fcG9pbnQoYWVzKGNvbG91ciA9IGNvdW50cnkpKSsNCiAgdGhlbWVfZGFyaygpDQpgYGANCg0KDQojIyMjINCX0LDQstC00LDQvdC90Y8g0L3QsCDRgdCw0LzQvtGB0YLRltC50L3RgyDRgNC+0LHQvtGC0YMuIA0KDQrQn9C+0LHRg9C00YPQstCw0YLQuCDQtNC40L3QsNC80ZbQutGDINCy0ZbQtNC90L7RgdC90L7Qs9C+INC60YDQuNGC0LXRgNGW0Y4gYHJhdGVgINC60ZbQu9GM0LrQvtGB0YLRliDQt9Cw0YXQstC+0YDRjtCy0LDQvdGMINC/0L4g0YDQvtC60LDRhSDQtNC70Y8g0LrQvtC20L3QvtGXINC00LXRgNC20LDQstC4Lg0KDQpgYGB7cn0NCnRhYmxlMSAlPiUgICMg0YHRgtCw0L3QtNCw0YDRgtC90LjQuSDQvdCw0LHRltGAINC00LDQvdC40YUNCiAgbXV0YXRlKHJhdGUgPSBjYXNlcyAvIHBvcHVsYXRpb24gKiAxMDAwMCkgfD4gDQogIGdncGxvdChhZXMoeWVhciwgcmF0ZSkpICsNCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IGNvdW50cnkpLCBjb2xvdXIgPSAid2hpdGUiKSArIA0KICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSBjb3VudHJ5KSkrDQogIHRoZW1lX2RhcmsoKQ0KYGBgDQoNCiMjIyBHYXRoZXJpbmcNCg0KYGBge3J9DQp0YWJsZTRhDQpgYGANCg0KYGBge3J9DQp0YWJsZTRhICU+JSANCiAgZ2F0aGVyKGAxOTk5YCwgYDIwMDBgLCBrZXkgPSAieWVhciIsIHZhbHVlID0gImNhc2VzIikNCmBgYA0KDQpgYGB7cn0NCnRpZHk0YSA8LSB0YWJsZTRhICU+JSANCiAgZ2F0aGVyKGAxOTk5YCwgYDIwMDBgLCBrZXkgPSAieWVhciIsIHZhbHVlID0gImNhc2VzIikNCnRpZHk0YiA8LSB0YWJsZTRiICU+JSANCiAgZ2F0aGVyKGAxOTk5YCwgYDIwMDBgLCBrZXkgPSAieWVhciIsIHZhbHVlID0gInBvcHVsYXRpb24iKQ0KZHBseXI6OmxlZnRfam9pbih0aWR5NGEsIHRpZHk0YikNCmBgYA0KDQojIyMg0JfQsNCy0LTQsNC90L3RjyDQvdCwINGB0LDQvNC+0YHRgtGW0LnQvdGDINGA0L7QsdC+0YLRgw0KDQrQktC40LrQvtC90LDRgtC4INC/0L7Qv9C10YDQtdC00L3RlCDQt9Cw0LLQtNCw0L3QvdGPLCDQsdCw0LfRg9GO0YfQuNGB0Ywg0L3QsCDRgtCw0LHQu9C40YbRj9GFIGB0aWR5NGFgINGWIGB0aWR5NGJgINC3INCy0LjQutC+0YDQuNGB0YLQsNC90L3Rj9C8INC/0L7RgtC+0LrQvtCy0L7Qs9C+INC+0L/QtdGA0LDRgtC+0YDQsC4NCg0KYGBge3J9DQpkcGx5cjo6bGVmdF9qb2luKHRhYmxlNGEgfD4gDQogICAgICAgICAgICAgICAgICBnYXRoZXIoYDE5OTlgLCBgMjAwMGAsIGtleSA9ICJ5ZWFyIiwgdmFsdWUgPSAiY2FzZXMiKQ0KICAgICAgICAgICAgICAgICAsdGFibGU0YiB8PiANCiAgICAgICAgICAgICAgICAgICBnYXRoZXIoYDE5OTlgLCBgMjAwMGAsIGtleSA9ICJ5ZWFyIiwgdmFsdWUgPSAicG9wdWxhdGlvbiIpKSB8PiANCiAgbXV0YXRlKHJhdGUgPSBjYXNlcyAvIHBvcHVsYXRpb24gKiAxMDAwMCkgfD4gDQogIGdncGxvdChhZXMoeWVhciwgcmF0ZSkpICsNCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IGNvdW50cnkpLCBjb2xvdXIgPSAid2hpdGUiKSArIA0KICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSBjb3VudHJ5KSkrDQogIHRoZW1lX2RhcmsoKQ0KYGBgDQoNCiMjIyBTcHJlYWRpbmcNCg0KYGBge3J9DQp0YWJsZTINCmBgYA0KDQpgYGB7cn0NCnRhYmxlMiAlPiUNCiAgICBzcHJlYWQoa2V5ID0gdHlwZSwgdmFsdWUgPSBjb3VudCkNCmBgYA0KDQojIyMgU2VwYXJhdGluZw0KDQpgYGB7cn0NCnRhYmxlMw0KYGBgDQoNCmBgYHtyfQ0KdGFibGUzICU+JSANCiAgc2VwYXJhdGUocmF0ZSwgaW50byA9IGMoImNhc2VzIiwgInBvcHVsYXRpb24iKSkNCmBgYA0KDQojIyMgVW5pdGluZw0KDQpgYGB7cn0NCnRhYmxlNQ0KYGBgDQoNCmBgYHtyfQ0KdGFibGU1ICU+JSANCiAgdW5pdGUoeWVhciwgY2VudHVyeSwgeWVhciwgc2VwID0gIiIpDQpgYGANCg0KDQojIyMg0J/RgNC+0L/Rg9GJ0LXQvdGWINC30L3QsNGH0LXQvdC90Y8NCg0K0J/RgNC+0L/Rg9GJ0LXQvdGWINC30L3QsNGH0LXQvdC90Y8gKF9fbWlzc2luZyB2YWx1ZV9fKSDRgyDQvdCw0LHQvtGA0LDRhSDQtNCw0L3QuNGFINC80L7QttGD0YLRjCDQsdGD0YLQuCDQtNCy0L7RhSDQstC40LTRltCyOiBf0Y/QstC90ZZfICjQv9C+0LfQvdCw0YfQtdC90ZYg0Y/QuiBgTkFgLCBgTm90IEF2YWlsYWJsZWApINGWIF/QvdC10Y/QstC90ZZfICjQv9GA0L7RgdGC0L4g0L3QtSDQv9GA0LXQtNGB0YLQsNCy0LvQtdC90ZYg0YMg0LTQsNC90LjRhSkuINCi0LDQutGWINC00LDQvdGWINC90LDQt9C40LLQsNGO0YLRjNGB0Y8gX9C90LXQutC+0LzQv9C70LXQutGC0L3Rll8uICAgDQrQndC40LbRh9C1INC90LDQstC10LTQtdC90L4g0L/RgNC40LrQu9Cw0LQsINGP0LrQuNC5INGG0LUg0ZbQu9GO0YHRgtGA0YPRlC4gIA0KDQpgYGB7cn0NCnN0b2NrcyA8LSB0aWJibGUoDQogIHllYXIgICA9IGMoMjAxNSwgMjAxNSwgMjAxNSwgMjAxNSwgMjAxNiwgMjAxNiwgMjAxNiksDQogIHF0ciAgICA9IGMoICAgMSwgICAgMiwgICAgMywgICAgNCwgICAgMiwgICAgMywgICAgNCksDQogIHJldHVybiA9IGMoMS44OCwgMC41OSwgMC4zNSwgICBOQSwgMC45MiwgMC4xNywgMi42NikNCikNCnN0b2Nrcw0KYGBgDQoNCtCU0LDQvdGWINC30LAg0YfQtdGC0LLQtdGA0YLQuNC5INC60LLQsNGA0YLQsNC7IDIwMTUg0Y/QstC90L4g0LLRltC00YHRg9GC0L3RliDQv9GA0L4g0YnQviDRgdCy0ZbQtNGH0LjRgtGMINCy0ZbQtNC/0L7QstGW0LTQvdC1INC30L3QsNGH0LXQvdC90Y8uINCU0LDQvdGWINC30LAg0L/QtdGA0YjQuNC5INC60LLQsNGA0YLQsNC7INC90LUg0LLQvdC10YHQtdC90ZYg0YMg0YLQsNCx0LvQuNGG0Y4sINGC0L7QsdGC0L4g0LLRltC00YHRg9GC0L3RliDQvdC10Y/QstC90L4sINCw0LvQtSDQstGW0LTRgdGD0YLQvdGW0YHRgtGMINC80L7QttC90LAg0L/QvtC80ZbRgtC40YLQuCDQv9GW0YHQu9GPINCy0ZbQtNC/0L7QstGW0LTQvdC+0Zcg0YLRgNCw0YHQvdGE0L7RgNC80LDRhtGW0ZcuICANCg0KYGBge3J9DQpzdG9ja3MgJT4lIA0KICBzcHJlYWQoeWVhciwgcmV0dXJuKQ0KYGBgDQoNCtCS0LjRj9Cy0LjRgtC4INC80L3QvtC20LjQvdGDINC90LXQutC+0LzQv9C70LXQutGC0L3QuNGFINC00LDQvdC40YUg0LzQvtC20L3QsCDRgtCw0LrQvtC2INC3INCy0LjQutC+0YDQuNGB0YLQsNC90L3Rj9C8INGE0YPQvdC60YbRltGXIGBjb21wbGV0ZSgpYC4gIA0KDQpgYGB7cn0NCnN0b2NrcyAlPiUgDQogIGNvbXBsZXRlKHllYXIsIHF0cikNCmBgYA0KDQoNCtCf0YDQvtCx0LvQtdC80LAg0L3QtdC60L7QvNC/0LvQtdC60YLQvdC40YUg0LTQsNC90LjRhSDQstC40YDRltGI0YPRlNGC0YzRgdGPINC00LLQvtC80LAg0YjQu9GP0YXQsNC80Lg6INCy0LjQutC70Y7Rh9C10L3QvdGP0Lwg0L3QtdC60L7QvNC/0LvQtdC60YLQvdC40YUg0YHQv9C+0YHRgtC10YDQtdC20LXQvdGMLCDQsNCx0L4g0ZbQvNC/0YPRgtCw0YbRltGU0Y4g0L/RgNC+0L/Rg9GJ0LXQvdC40YUg0LfQvdCw0YfQtdC90Ywg0ZbQvdGI0LjQvNC4INC30L3QsNGH0LXQvdC90Y/QvNC4LCDQstC40YXQvtC00Y/Rh9C4INC3INC/0LXQstC90L7RlyDQvNC+0LTQtdC70ZYuICANCg0KYGBge3J9DQpzdG9ja3MgJT4lIA0KICBzcHJlYWQoeWVhciwgcmV0dXJuKSAlPiUgDQogIGdhdGhlcih5ZWFyLCByZXR1cm4sIGAyMDE1YDpgMjAxNmAsIG5hLnJtID0gVFJVRSkNCmBgYA0KDQrQoyDQstC40L/QsNC00LrQsNGFLCDQutC+0LvQuCDRhtC1INC00L7RhtGW0LvRjNC90L4sINC80L7QttC90LAg0LLQuNC60L7RgNC40YHRgtC+0LLRg9Cy0LDRgtC4INGE0YPQvdC60YbRltGOIGBmaWxsKClgLCDRj9C60LAg0LfQsNC/0L7QstC90Y7RlCDQv9GA0L7Qv9GD0YnQtdC90L3RliDQt9C90LDRh9C10L3QvdGPLCDQstC30Y/QstGI0Lgg0LfQvdCw0YfQtdC90L3RjyDQtyDQvtGB0YLQsNC90L3RjNC+0Zcg0LfQsNC/0L7QstC90LXQvdC+0Zcg0LrQu9GW0YLQuNC90LrQuDogIA0KDQpgYGB7cn0NCmRmIDwtIGRhdGEuZnJhbWUoTW9udGggPSAxOjEyLCBZZWFyID0gYygyMDAwLCByZXAoTkEsIDExKSkpDQpkZg0KZGYgJT4lIGZpbGwoWWVhcikNCmBgYA0KDQojIyMg0KLRgNCw0L3RgdGE0L7RgNC80LDRhtGW0Y8NCg0KYGBge3J9DQojINCS0LjQsdGW0YDQutCwINGA0Y/QtNC60ZbQsiDRgtCw0LHQu9C40YbRlg0KbGlicmFyeShkcGx5cikNCg0Kc3RhcndhcnMgJT4lIA0KICBmaWx0ZXIoc3BlY2llcyA9PSAiRHJvaWQiKQ0KYGBgDQoNCmBgYHtyfQ0KIyDQktC40LHRltGA0LrQsCDQv9C+0LvRltCyINGC0LDQsdC70LjRhtGWDQpzdGFyd2FycyAlPiUgDQogIHNlbGVjdChuYW1lLCBlbmRzX3dpdGgoImNvbG9yIikpDQpgYGANCg0KYGBge3J9DQojINCh0YLQstC+0YDQtdC90L3RjyDQvdC+0LLQvtCz0L4g0L/QvtC70Y8g0YMg0YLQsNCx0LvQuNGG0ZYg0Lcg0L/QvtGB0LvRltC00YPRjtGH0L7RjiDQstC40LHRltGA0LrQvtGODQpzdGFyd2FycyAlPiUgDQogIG11dGF0ZShuYW1lLCBibWkgPSBtYXNzIC8gKChoZWlnaHQgLyAxMDApICBeIDIpKSAlPiUNCiAgc2VsZWN0KG5hbWU6bWFzcywgYm1pKQ0KYGBgDQoNCmBgYHtyfQ0KIyDQodC+0YDRgtGD0LLQsNC90L3RjyDQtNCw0L3QuNGFDQpzdGFyd2FycyAlPiUgDQogIGFycmFuZ2UoZGVzYyhtYXNzKSkNCmBgYA0KDQpgYGB7cn0NCiMg0J7QsdGH0LjRgdC70LXQvdC90Y8g0LDQs9GA0LXQs9Cw0YLRltCyINC3INC/0L7Qv9C10YDQtdC00L3RltC8INCz0YDRg9C/0YPQstCw0L3QvdGP0Lwg0L/QviDQv9C+0LvRjiBzcGVjaWVzDQpzdGFyd2FycyAlPiUNCiAgZ3JvdXBfYnkoc3BlY2llcykgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBuID0gbigpLA0KICAgIG1hc3MgPSBtZWFuKG1hc3MsIG5hLnJtID0gVFJVRSkNCiAgKSAlPiUNCiAgZmlsdGVyKG4gPiAxKQ0KYGBgDQoNCiMjINCG0L3QtNC40LLRltC00YPQsNC70YzQvdC1INC30LDQstC00LDQvdC90Y8NCg0KYGBge3J9DQpsaWJyYXJ5KHJlYWR4bCkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkoRFQpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCg0KIyDQl9Cw0LLQsNC90YLQsNC20LXQvdC90Y8g0LTQsNC90LjRhSDRltC3INC+0YfQuNGJ0LXQvdC+0LPQviBFeGNlbCDRhNCw0LnQu9GDDQpkYXRhIDwtIHJlYWRfZXhjZWwoImNsZWFuZWRfY3VycmVuY3lfZGF0YS54bHN4IikNCg0KIyDQlNC+0LTQsNGU0LzQviDQutC+0LvQvtC90LrRgyDQtyDQvNGW0YHRj9GG0LXQvCDQtNC70Y8g0YHQtdGA0LXQtNC90YzQvtC80ZbRgdGP0YfQvdC40YUg0LfQvdCw0YfQtdC90YwNCmRhdGEgPC0gZGF0YSAlPiUgbXV0YXRlKNC80ZbRgdGP0YbRjCA9IGZsb29yX2RhdGUo0JTQsNGC0LAsICJtb250aCIpKQ0KDQojINCg0L7Qt9GA0LDRhdGD0L3QvtC6INGB0LXRgNC10LTQvdGM0L7QvNGW0YHRj9GH0L3QuNGFINC60YPRgNGB0ZbQsg0KbW9udGhseV9hdmcgPC0gZGF0YSAlPiUNCiAgZ3JvdXBfYnko0LzRltGB0Y/RhtGMKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIFVTRF9VQUhfYXZnID0gbWVhbihVU0RfVUFILCBuYS5ybSA9IFRSVUUpLA0KICAgIEVVUl9VQUhfYXZnID0gbWVhbihFVVJfVUFILCBuYS5ybSA9IFRSVUUpLA0KICAgIFJVQl9VQUhfYXZnID0gbWVhbihSVUJfVUFILCBuYS5ybSA9IFRSVUUpDQogICkNCg0KIyDQhtC90YLQtdGA0LDQutGC0LjQstC90LAg0YLQsNCx0LvQuNGG0Y8g0YnQvtC00LXQvdC90LjRhSDQutGD0YDRgdGW0LINCmRhaWx5X3RhYmxlIDwtIGRhdGF0YWJsZShkYXRhLCBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gMTAsIGF1dG9XaWR0aCA9IFRSVUUpKQ0KDQojINCG0L3RgtC10YDQsNC60YLQuNCy0L3QsCDRgtCw0LHQu9C40YbRjyDRgdC10YDQtdC00L3RjNC+0LzRltGB0Y/Rh9C90LjRhSDQutGD0YDRgdGW0LINCm1vbnRobHlfdGFibGUgPC0gZGF0YXRhYmxlKG1vbnRobHlfYXZnLCBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gMTAsIGF1dG9XaWR0aCA9IFRSVUUpKQ0KDQojINCG0L3RgtC10YDQsNC60YLQuNCy0L3QuNC5INCz0YDQsNGE0ZbQuiDRidC+0LTQtdC90L3QuNGFINC60YPRgNGB0ZbQsg0KZGFpbHlfcGxvdCA8LSBwbG90X2x5KGRhdGEsIHggPSB+0JTQsNGC0LApICU+JQ0KICBhZGRfbGluZXMoeSA9IH5VU0RfVUFILCBuYW1lID0gIlVTRC9VQUgiKSAlPiUNCiAgYWRkX2xpbmVzKHkgPSB+RVVSX1VBSCwgbmFtZSA9ICJFVVIvVUFIIikgJT4lDQogIGFkZF9saW5lcyh5ID0gflJVQl9VQUgsIG5hbWUgPSAiUlVCL1VBSCIpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAi0KnQvtC00LXQvdC90LAg0LTQuNC90LDQvNGW0LrQsCDQutGD0YDRgdGW0LIg0LLQsNC70Y7RgiIsIHhheGlzID0gbGlzdCh0aXRsZSA9ICLQlNCw0YLQsCIpLCB5YXhpcyA9IGxpc3QodGl0bGUgPSAi0JrRg9GA0YEiKSkNCg0KIyDQhtC90YLQtdGA0LDQutGC0LjQstC90LjQuSDQs9GA0LDRhNGW0Log0YHQtdGA0LXQtNC90YzQvtC80ZbRgdGP0YfQvdC40YUg0LrRg9GA0YHRltCyDQptb250aGx5X3Bsb3QgPC0gcGxvdF9seShtb250aGx5X2F2ZywgeCA9IH7QvNGW0YHRj9GG0YwpICU+JQ0KICBhZGRfbGluZXMoeSA9IH5VU0RfVUFIX2F2ZywgbmFtZSA9ICJVU0QvVUFIIikgJT4lDQogIGFkZF9saW5lcyh5ID0gfkVVUl9VQUhfYXZnLCBuYW1lID0gIkVVUi9VQUgiKSAlPiUNCiAgYWRkX2xpbmVzKHkgPSB+UlVCX1VBSF9hdmcsIG5hbWUgPSAiUlVCL1VBSCIpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAi0KHQtdGA0LXQtNC90YzQvtC80ZbRgdGP0YfQvdCwINC00LjQvdCw0LzRltC60LAg0LrRg9GA0YHRltCyINCy0LDQu9GO0YIiLCB4YXhpcyA9IGxpc3QodGl0bGUgPSAi0JzRltGB0Y/RhtGMIiksIHlheGlzID0gbGlzdCh0aXRsZSA9ICLQodC10YDQtdC00L3RltC5INC60YPRgNGBIikpDQoNCiMg0JLQuNCy0LXQtNC10L3QvdGPINGC0LDQsdC70LjRhtGMINGWINCz0YDQsNGE0ZbQutGW0LINCnByaW50KGRhaWx5X3RhYmxlKQ0KcHJpbnQobW9udGhseV90YWJsZSkNCmRhaWx5X3Bsb3QNCm1vbnRobHlfcGxvdA0KDQoNCmBgYA==